#!/bin/sh
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# This script downloads and installs the basic packages that the user needs
# in developer mode. It also takes care of some configuration details
# that arise from not havin write access to the root filesystem.

# Constant definitions.
P_CONFIG_CROS=/etc/portage
P_CONFIG_DEVELOPER=/usr/local/etc/portage
EMERGE_PACKAGES="${P_CONFIG_CROS}/bootstrap.packages"

# Global variables.
BINHOST=
CHROMEOS_DEVSERVER=
CHROMEOS_RELEASE_BOARD=

# Process flags.
. /usr/share/misc/shflags || exit 1
DEFINE_string binhost "" \
  "URL of the binhost that emerge will use." b
DEFINE_string binhost_version "" \
  "Version number to use instead of the one in /etc/lsb-release."
DEFINE_boolean reinstall "${FLAGS_FALSE}" \
  "Remove all installed packages and re-bootstrap emerge."
DEFINE_boolean uninstall "${FLAGS_FALSE}" \
  "Remove all installed packages."
DEFINE_boolean yes "${FLAGS_FALSE}" \
  "Do not prompt for input. Assumes yes's to all responses." y

FLAGS "$@" || exit 1

set -e

# Get the portage configuration variables.
. "${P_CONFIG_CROS}/make.profile/make.defaults"

# Echo's args to stderr and prefixes with ERROR(dev_install).
error() {
  echo "ERROR(dev_install): $*" >&2
}

# Reads the user's reply and returns 0 if the user responds with y.
# Accepts input like echo for the user prompt i.e. $@.
yes_or_no() {
  [ ${FLAGS_yes} -eq ${FLAGS_TRUE} ] && return 0
  local reply
  read -p "$*? (y/N) " reply
  [ "${reply}" = "y" ]
}

# Returns value of variable $2 from /etc/lsb-like file $1.
# $1 - Path to the /etc/lsb-like file.
# $2 - Variable name.
# Returns value or an empty string.
get_lsb_var() {
  sed -n "s/^$2=//p" "$1"
}

# Here we simply wipe /usr/local/* since that it's the only directory in which
# packages are installed to.
remove_installed_packages() {
  echo "To clean up, we will run:"
  echo "  rm -rf /usr/local/"
  echo "Any content you have stored in there will be deleted."
  if yes_or_no "Remove all installed packages now"; then
    rm -rf --one-file-system /usr/local/*
    echo "Removed all installed packages."
  else
    echo "Operation cancelled."
    exit 0
  fi
}

# Parses flags to return the binhost or if none set, use the default binhost
# set up from installation.
get_binhost() {
  if [ -n "${FLAGS_binhost}" ]; then
    BINHOST="${FLAGS_binhost}"
  elif [ -n "${CHROMEOS_DEVSERVER}" ]; then
    echo "Devserver URL set to:  ${CHROMEOS_DEVSERVER}"
    if yes_or_no "Use it as the binhost"; then
      local packages_path="static/pkgroot/${CHROMEOS_RELEASE_BOARD}/packages"
      BINHOST="${CHROMEOS_DEVSERVER}/${packages_path}"
    fi
  fi
  if [ -z "${BINHOST}" ]; then
    local release_number
    # Get prefix for binhost.
    . "${P_CONFIG_CROS}/repository.conf"

    if [ -n "${FLAGS_binhost_version}" ]; then
      release_number="${FLAGS_binhost_version}"
    else
      # Get the release number.
      local cr_version="$(/opt/google/chrome/chrome --version)"
      # Chrome version looks like (Google Chrome/Chromium) branch.0.a.b
      local cr_branch="$(echo "${cr_version}" |
          sed -r 's/.*[[:space:]]([0-9]+)\..*/\1/')"
      local version="$(get_lsb_var /etc/lsb-release CHROMEOS_RELEASE_VERSION)"
      release_number="R${cr_branch}-${version}"
    fi
    BINHOST="${PREFIX_BINHOST}canary-${release_number}/packages"
  fi
}

# Get the static Python binary and portage (emerge script and python modules).
download_bootstrap_packages() {
  # Download packages that python/emerge require into /usr/local/bootsrap.
  while read line; do
    local package_url="${BINHOST}/${line}.tbz2"
    local directory="$(echo "${line}" | cut -d "/" -f 1)"
    local package_file="${PKGDIR}/${line}.tbz2"
    mkdir -p -m 0755 "${PKGDIR}/${directory}"
    echo "Downloading ${package_url}"
    if ! curl --fail -o "${package_file}" "${package_url}"; then
      error "Could not download package."
      error "Command curl --fail -o ${package_file} ${package_url} failed."
      return 1
    fi
    echo "Extracting ${package_file}"
    # TODO(sosa): pipeline our download/unpack cycles.
    # Ignore std error output about trailing garbage after EOF.
    if ! tar -C /usr/local/ -xjkf "${package_file}" 2>/dev/null; then
      error "Could not extract package."
      error "Command tar -C /usr/local -xjf ${package_file} failed."
      return 1
    fi
  done < "${EMERGE_PACKAGES}"
}

# Configure emerge in /usr/local.
configure_emerge() {
  # Copy emerge configuration to /usr/local.
  mkdir -p -m 0755 "${P_CONFIG_DEVELOPER}"
  cp "${P_CONFIG_CROS}/make.profile/make.conf" "${P_CONFIG_DEVELOPER}"
  # The source files might be symlinks, and we'll probably clobber/update things
  # ourselves locally, so dereference all the symlinks for our local copy.
  cp -rfL "${P_CONFIG_CROS}/make.profile" "${P_CONFIG_DEVELOPER}/make.profile"

  # Create the directories defined in the portage config files. Permissions are
  # consistent with the other directories in /usr/local, which is a bind mount
  # for /mnt/stateful_partition/dev_image.
  mkdir -p -m 0755 "${PORTDIR}"
  mkdir -p -m 0755 "${PKGDIR}"
  mkdir -p -m 0755 "${DISTDIR}"
  mkdir -p -m 0755 "${RPMDIR}"
  mkdir -p -m 0755 "${PORTAGE_TMPDIR}"
  mkdir -p -m 0755 "${BUILD_PREFIX}"
  echo "PORTAGE_BINHOST=${BINHOST}" >> "${P_CONFIG_DEVELOPER}/make.conf"

  # Add LD_LIBRARY_PATH within ebuild.sh.
  # TODO(arkaitzr): find out a cleaner way to do this.
  sed -i "3 a\export LD_LIBRARY_PATH=\"${LD_LIBRARY_PATH}\"" \
      /usr/local/lib/portage/*/ebuild.sh

  # Check for package.provided from $BINHOST.
  local package_provided="${BINHOST}/package.provided"
  local tmp_provided="$(mktemp)"

  if ! curl --fail -o "${tmp_provided}" "${package_provided}" &&
     [ -n "${CHROMEOS_DEVSERVER}" ]; then
    echo "${package_provided} not found"

    package_provided="${CHROMEOS_DEVSERVER}/"\
"static/pkgroot/${CHROMEOS_RELEASE_BOARD}/"\
"/etc/portage/profile/package.provided"
    echo "Fall back to search ${package_provided}"

    if ! curl --fail -o "${tmp_provided}" "${package_provided}"; then
      echo "${package_provided} not found"
    fi
  fi
  if [ -s ${tmp_provided} ]; then
    mv "${tmp_provided}" \
        "${P_CONFIG_DEVELOPER}/make.profile/package.provided/gsutil_provided"
  fi
}

install_optional_packages() {
  # Install gmerge manually first (even though it's part of the default
  # virtual/target-os-dev) since it provides its own list of package.provided
  # files.  Other packages that virtual/target-os-dev pulls in might rely on
  # those lists, so we need them sooner rather than later.
  emerge gmerge
  if yes_or_no "Install virtual/target-os-dev package now"; then
    emerge virtual/target-os-dev
  else
    echo "You can install virtual/target-os-dev later by typing the command:" \
         "emerge virtual/target-os-dev"
  fi
}

import_lsb_release() {
  local override
  CHROMEOS_DEVSERVER=$(get_lsb_var /etc/lsb-release CHROMEOS_DEVSERVER)
  CHROMEOS_RELEASE_BOARD=$(get_lsb_var /etc/lsb-release CHROMEOS_RELEASE_BOARD)

  # Check local overrides.
  if [ -f /usr/local/etc/lsb-release ]; then
    override=$(get_lsb_var /usr/local/etc/lsb-release CHROMEOS_DEVSERVER)
    [ -n "${override}" ] && CHROMEOS_DEVSERVER="${override}"
    override=$(get_lsb_var /usr/local/etc/lsb-release CHROMEOS_RELEASE_BOARD)
    [ -n "${override}" ] && CHROMEOS_RELEASE_BOARD="${override}"
  fi
}

main() {
  # Check if we are root.
  if [ $(id -u) -ne 0 ]; then
    error "Can not run dev_install."
    error "You are not root or you did not use sudo."
    return 1
  fi

  # Sanity check.  People often run `su` or `sudo su` and leave off that final
  # - which means they don't get a proper environment.  Catch them here rather
  # than fail randomly midway through the process.
  case :${PATH}: in
  *:/usr/local/bin:*) ;;
  *)
    error "Your environment appears to be incomplete.  When changing to root,"
    error "did you remember to run the full command (don't forget the dash):"
    error " $ sudo su -"
    return 1
    ;;
  esac

  # This script should only run in developer mode or for developer images.
  if crossystem "cros_debug?0"; then
    error "Can not run dev_install."
    error "Chrome OS is not in developer mode."
    return 1
  fi

  if [ ${FLAGS_uninstall} -eq ${FLAGS_TRUE} \
       -o ${FLAGS_reinstall} -eq ${FLAGS_TRUE} ]; then
    remove_installed_packages
    if [ ${FLAGS_uninstall} -eq ${FLAGS_TRUE} ]; then
      if [ ${FLAGS_reinstall} -eq ${FLAGS_TRUE} ]; then
        error "Reinstall specified but ignored as uninstall was also specified."
      fi
      return 0
    else
      echo "Reinstalling emerge."
    fi
  fi

  # Check if packages are already installed.
  if [ -d "${P_CONFIG_DEVELOPER}" ]; then
    error "Directory ${P_CONFIG_DEVELOPER} exists."
    error "Did you mean dev_install --reinstall?"
    return 1
  fi

  # Create this loop so uncompressed files in /usr/local/usr/* will be reachable
  # through /usr/local*.
  # Note: /usr/local is mount-binded onto the /mnt/stateful_partition/dev_mode
  # during chromeos_startup during boot for machines in dev_mode.
  if [ ! -d /usr/local/usr ]; then
    ln -s . /usr/local/usr
  fi

  if [ ! -d /usr/local/local ]; then
    ln -s . /usr/local/local
  fi

  # TODO(sosa): Remove hack once lib dependencies are sorted in R27.
  if [ ! -d /usr/local/lib64 ]; then
    mkdir -p /usr/local/lib
    ln -s lib /usr/local/lib64
  fi

  # Set up symlinks for etc/{group,passwd}, so that packages can look up users
  # and groups correctly.
  if [ ! -d /usr/local/etc ]; then
    mkdir -p /usr/local/etc
  fi
  if [ ! -f /usr/local/etc/group ]; then
    ln -s ../../../etc/group /usr/local/etc/group
  fi
  if [ ! -f /usr/local/etc/passwd ]; then
    ln -s ../../../etc/passwd /usr/local/etc/passwd
  fi

  echo "Starting installation of developer packages."
  echo "First, we download the necessary files."
  import_lsb_release
  get_binhost
  download_bootstrap_packages

  echo "Files downloaded, configuring emerge."
  configure_emerge

  echo "Emerge installation complete. Installing additional optional packages."
  install_optional_packages

  echo "dev_install done. Enjoy!"
}

main
